/*
Copyright (c) 2021, xgaodongyu@163.com
All rights reserved.

Redistribution and use in source and binary forms, with or without
        modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
        IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
        FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
        DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
        SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
        CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifndef STRUCT_VIEWER
#define STRUCT_VIEWER

#include <cstdint>
#include <cstdio>
#include <cstring>
#include <list>

namespace struct_view {
    class StructPath : public std::list<const char*> {
    public:
        StructPath(const char* f, int l) : file{f}, line{l}{}
        auto OutputPath(char* output, uint32_t MAX_SIZE) const -> char* {
            uint32_t offset = 0;
            for (auto p : (*this)) {
                auto len = strlen(p);
                if ((offset + len) >= MAX_SIZE) break;
                ::memcpy(output + offset, p, len);
                offset += len;
                output[offset++] = '.';
            }
            output[offset - 1] = '\0';
            return output;
        }

        const char* file;
        int line;
    };

    template<typename T>
    class ViewPrint {
    public:
        static auto onFail(T const& expect, T const& truly, StructPath const& path) -> void {
            static constexpr uint32_t size = 500;
            char name[size];
            ::printf("param %s match fail!\n", path.OutputPath(name, size));
        };
    };

    class View {
    public:
        virtual auto Equal(StructPath&) -> bool = 0;
        virtual auto Assign() -> void = 0;
        virtual ~View(){};
    };

    template<typename ElemType>
    class ArrayView {
    public:
        virtual auto Equal(ElemType&, StructPath&) -> bool = 0;
        virtual auto Assign(ElemType&) -> void = 0;
        virtual ~ArrayView(){};
    };

    template<typename TOVIEW>
    class LambdaToView {
    public:
        template<typename PARENT>
        auto ToView(PARENT& parent, TOVIEW& view) -> View* {
            return view(parent);
        }
    };

    using ViewList_p = std::list<View*>;

    template<typename ElemType>
    using ArrayViewList_p = std::list<ArrayView<ElemType>*>;

    namespace details {
        template<typename T, typename COMPARE>
        class CheckTrait : public View {
            using ObjType = T;
            using RealType = typename std::remove_reference<T>::type;

        public:
            CheckTrait(ObjType& sel, COMPARE&& value, const char* _name) :
            select{sel}, value{std::move(value)}, name{_name} {}

			auto Assign() -> void override {
				select = value;
			}

            auto Equal(StructPath& path) -> bool override {
                path.push_back(name);
                if (select != value) {
                    ViewPrint<RealType>::onFail(static_cast<RealType>(value), select, path);
                    path.pop_back();
                    return false;
                }
                path.pop_back();
                return true;
            }

            ~CheckTrait() {}

        private:
            ObjType& select;
            COMPARE&& value;
            const char* name;
        };

        template<typename ElemType>
        class ArrayElemTrait : public ArrayView<ElemType> {
            using RealType = typename std::remove_reference<ElemType>::type;
        public:
            ArrayElemTrait() = default;
            auto Equal(RealType& check, StructPath& path) -> bool override {
                value = check;
                auto isEqual = true;
                for (auto&& v : tree) {
                    if (!v->Equal(path)) isEqual = false;
                }
                return isEqual;
            }
            auto Assign(RealType& assign) -> void override {
                for (auto&& v : tree) {
                    v->Assign();
                }
                assign = value;
            }

            auto GetValue() -> RealType& {
                return value;
            }

            auto AppendChild(View* c) -> void {
                tree.push_back(c);
            }

            ViewList_p tree;

        private:
            RealType value;
        };

        template<typename ELEM, typename SIZE, bool ORDER>
        class ArrayTrait : public View {
            using RealType = typename std::remove_reference<ELEM>::type;
        public:
            ArrayTrait(RealType *sel, SIZE& size, const char* _name) :
                    select{sel}, size{size}, name{_name} {}

            auto Assign() -> void override {
                size = tree.size();
                auto i = 0;
                for (auto&& v : tree) {
                    v->Assign(select[i]);
                }
            }

            auto Equal(StructPath& path) -> bool override {
                if (tree.size() != size) {
                    return false;
                }
                path.push_back(name);
                auto isEqual = true;
                auto i = 0;
                for (auto&& v : tree) {
                    if (!v->Equal(select[i], path)) isEqual = false;
                }
                path.pop_back();
                return isEqual;
            }

            auto AppendChild(ArrayView<RealType>* c) -> void {
                tree.push_back(c);
            }

            ~ArrayTrait() {}

        public:
            ArrayViewList_p<RealType> tree;
        private:
            RealType *select;
            SIZE& size;
            const char* name;
        };

        template<typename T>
        class ViewTrait : public View {
            using ObjType = T;

        public:
            ViewTrait(const char* _name) : name{_name} {}

            auto Equal(StructPath& path) -> bool override {
                path.push_back(name);
                auto isEqual = true;
                for (auto&& v : tree) {
                    if (!v->Equal(path)) isEqual = false;
                }
                path.pop_back();
                return isEqual;
            }

            auto Assign() -> void override {
                for (auto&& v : tree) {
                    v->Assign();
                }
            }

            auto AppendChild(View* c) -> void {
                tree.push_back(c);
            }

            ~ViewTrait() {
                for (auto&& v : tree) {
                    delete v;
                }
            }
        public:
            ViewList_p tree;
        private:
            const char* name;
        };
    }
}

///////////////////////////////////////////////////////////////////////
#define __To_DeF_nAmE__(name) ViEwfRaG##name
#define __ViEw_NaMe__(name) #name

#define __DeCl_TyPe__(type, param) using type = decltype(param)

#define __NeW_vIeW__(param) \
auto* view = new struct_view::details::ViewTrait<ViewType>(__ViEw_NaMe__(param))

#define __NeW_aRrAy__(param) \
auto* view = new struct_view::details::ArrayTrait<ViewType, SizeType, true>(parent, size, __ViEw_NaMe__(param))

#define __NeW_aRaAy_ElEm__(param) \
auto* view = new struct_view::details::ArrayElemTrait<RealType>()

#define __NeW_cHeCk__(param, value) \
auto* view = new struct_view::details::CheckTrait<ViewType, ValueType>(parent, std::move(value), __ViEw_NaMe__(param))

#define __ApPeNd_ViEw__(v) \
view->AppendChild(v)

#define __ReTuRn_ViEw__() return view

#define __DeF_cHeCk__(param, value)       \
[](auto&& parent) {                        \
    __DeCl_TyPe__(ViewType, parent);            \
    __DeCl_TyPe__(ValueType, value);            \
    __NeW_cHeCk__(param, value);           \
    __ReTuRn_ViEw__();                          \
}(parent.param)

#define __DeF_vIeW__(param, ...)          \
[](auto&& parent) {                        \
    __DeCl_TyPe__(ViewType, parent);            \
    __NeW_vIeW__(param);                   \
    __VA_ARGS__                             \
    ;__ReTuRn_ViEw__();                         \
}(parent.param)

#define __DeF_ArRaY_eLeM__(...) \
[](auto&& array) {                        \
    __DeCl_TyPe__(ViewType, array[0]);            \
    using RealType = typename std::remove_reference<ViewType>::type;            \
    __NeW_aRaAy_ElEm__();                   \
    auto&& parent = view->GetValue();        \
    __VA_ARGS__                             \
    ;__ReTuRn_ViEw__();                         \
}(parent)

#define __DeF_ArRaY__(array, num, ...) \
[](auto&& parent, auto&& size) {                        \
    __DeCl_TyPe__(ViewType, parent[0]);\
    __DeCl_TyPe__(SizeType, size);\
    __NeW_aRrAy__(array);                           \
    __VA_ARGS__                             \
    ;__ReTuRn_ViEw__();                     \
}(parent.array, parent.num)

///////////////////////////////////////////////////////////////////////
#define DECL_VIEW_FAIL_FORMAT(TypeName)                                                         \
template<> class struct_view::ViewPrint<TypeName> {                                  \
public:                                                                                         \
    static auto onFail(TypeName const&, TypeName const&, struct_view::StructPath& path) -> void;                  \
};

#define DEF_VIEW_FAIL_FORMAT_FUNC(TypeName)                                                     \
auto struct_view::ViewPrint<TypeName>::                                              \
onFail(TypeName const& expect, TypeName const& truly, struct_view::StructPath& path) -> void

#define DEF_VIEW_FAIL_FORMAT(TypeName)      \
DECL_VIEW_FAIL_FORMAT(TypeName)             \
DEF_VIEW_FAIL_FORMAT_FUNC(TypeName)

///////////////////////////////////////////////////////////////////////

#define DEF_DYNAMIC_VALUE(Type, Name, Value) \
static Type Name = Value

///////////////////////////////////////////////////////////////////////
#define VCHECK(param, value) \
__ApPeNd_ViEw__(__DeF_cHeCk__(param, value))

#define VVIEW(param, ...)                        \
__ApPeNd_ViEw__(__DeF_vIeW__(param, __VA_ARGS__))

#define VASSIGN(param, value) \
VCHECK(param, value)

#define VAPPLY(name) \
__ApPeNd_ViEw__(name{}.ToView<decltype(parent)>(parent, __To_DeF_nAmE__(name)))

#define VDEFINE(name, ...)                                          \
auto __To_DeF_nAmE__(name) = [](auto&& parent) {                       \
            __DeCl_TyPe__(ViewType, parent);                            \
            __NeW_vIeW__(name);                                        \
            __VA_ARGS__                                             \
            ;__ReTuRn_ViEw__();                                         \
        };                                                            \
using name = struct_view::LambdaToView<decltype(__To_DeF_nAmE__(name))>

#define VDEFINE_STRU(name, structure, ...) VDEFINE(name, ...)

#define VORARRAY(array, num, ...) \
__ApPeNd_ViEw__(__DeF_ArRaY__(array, num, __VA_ARGS__))

#define VARRAY(...) \
__ApPeNd_ViEw__(__DeF_ArRaY_eLeM__(__VA_ARGS__))

#define VSTRUCT(name, STRUCT, ...)                                     \
class name {                                                        \
private:                                                             \
    auto ToViews(STRUCT& parent) -> struct_view::View* {              \
        return [](auto&& parent) {                                 \
            __DeCl_TyPe__(ViewType, parent);                            \
            __NeW_vIeW__(STRUCT);                               \
            __VA_ARGS__                                             \
            ;__ReTuRn_ViEw__();                                         \
        }(parent);                                                  \
    }                                                               \
                                                                       \
public:                                                                \
    auto IsEqual(STRUCT& tobeView) -> bool {                          \
        auto* view = ToViews(tobeView);                                \
        bool ret = view->Equal(path);                                    \
        delete view;                                                    \
        return ret;                                                    \
    }                                                                  \
                                                                       \
    auto Assign(STRUCT& tobeAssign) -> void {                           \
        auto* view = ToViews(tobeAssign);                                \
        view->Assign();                                                \
        delete view;                                                   \
    }                                                                   \
private:                                                           \
    struct_view::StructPath path{ __FILE__, __LINE__};    \
}

#endif // STRUCT_VIEWER
